<?php


namespace app\service\DistributedMemcached;

/**
 * 分布式Memecached客户端实现，采用一致性hash算法
 * Class DistributedMemcached
 * @package app\service\DistributedMemcached
 */
class DistributedMemcached
{

    protected $nodes = [];
    protected $position = [];
    protected $mul = 64;

    private static $instance = null;

    /**
     * 构造
     * @param int $mul 虚拟节点数目
     */
    private function __construct(int $mul)
    {
        $this->mul = $mul;
    }

    // 私有化克隆
    private function __clone()
    {
    }

    /**
     * 单例方法
     * @param int $mul 虚拟节点数目
     * @return DistributedMemcached 单例
     */
    final public static function getInstance(int $mul = 64)
    {
        if (self::$instance instanceof self) {
            return self::$instance;
        }

        self::$instance = new self($mul);
        return self::$instance;
    }

    /**
     * 添加节点
     * @param MemNode $node 节点
     */
    private function addNode( MemNode $node)
    {
        if (isset($this->nodes[strval($node)])) return;

        for ($i = 0; $i < $this->mul; $i ++ ) {
            $pos = $this->hash(strval($node). "-".$i);
            $this->position[$pos] = $node;
            $this->nodes[strval($node)][] = $pos;
        }
        $this->sortPos();
    }

    /**
     * 删除节点
     * @param String $node 节点字符串 ip:port
     */
    private function delNode(string $node)
    {
        if (!isset($this->nodes[$node])) return;

        // 循环删除虚拟节点
        foreach ($this->nodes[$node] as $val) {
            unset($this->position[$val]);
        }

        // 删除节点
        unset($this->nodes[$node]);
    }

    /**
     * 根据key找出节点
     */
    public function getMemcachedClient(string $key)
    {
        $point = $this->hash($key);

        // 获取第一个节点，如果没找到，就是第一个节点，构成环
        $node = current($this->position);
        // 循环获取相近的节点
        foreach ($this->position as $key => $val) {
            if ($point <= $key) {
                $node = $val;
                break;
            }
        }

        reset($this->position);
        return $node;
    }

    /**
     * 加载节点
     * @param array $loadConf 数组数据 [['ip' => , 'port' => ]...]
     */
    public function loadForArray(array $loadConf)
    {
        foreach($loadConf as $_node)
        {
            if (!(isset($_node['ip']) && isset($_node['port'])))
                throw new \Exception("Error config");
            $memNode = new MemNode($_node['ip'], $_node['port']);
            $this->addNode($memNode);
        }
    }

    /**
     * 排序
     */
    private function sortPos()
    {
        ksort($this->position, SORT_REGULAR);
    }

    /**
     * 求字符串的32位hash
     * @param string $str 源字符串
     * @return string hash字符串
     */
    public function hash(string $str)
    {
        return sprintf("%u", \crc32($str));
    }
}

########## 实例 #################
/**
 * $obj = DistributedMemcached::getInstance();
$this->assertInstanceOf(DistributedMemcached::class, $obj);
$obj->loadForArray([
['ip' => '192.168.0.1', 'port'=> 7300],
['ip' => '192.168.0.2', 'port'=> 7300],
['ip' => '192.168.0.3', 'port'=> 7300],
['ip' => '192.168.0.4', 'port'=> 7300]
]);
$node = $obj->getMemcachedClient("hello");
$this->assertInstanceOf(MemNode::class, $node);
 */